#---------------------------------------------------------------------- # Program to support simple orthographic 3D projection and blending # between points and colors. # # Copyright (C) October 2018 -- Dr. William T. Verts #---------------------------------------------------------------------- def INT(N): return int(round(N)) def addCircleFilled (Canvas, X, Y, R, NewColor): addOvalFilled (Canvas, INT(X-R), INT(Y-R), INT(2*R), INT(2*R), NewColor) return #---------------------------------------------------------------------- # 3D Orthographic Projection functions #---------------------------------------------------------------------- Sine = 0.5 # 30 degrees Cosine = sqrt(3) / 2.0 # 30 degrees Scale2D = 10.0 Origin2D = [0,0] def Project3D (P3D): # P3D is 3D point, list [X,Y,Z] global Scale2D,Origin2D X = Origin2D[0] + (P3D[0] + P3D[2] * Cosine) * Scale2D Y = Origin2D[1] - (P3D[1] + P3D[2] * Sine) * Scale2D Result = [X,Y] return Result # Result is a 2D point, list [X,Y] def SetOrigin2D(P2D): global Origin2D Origin2D = P2D return def SetScale2D(NewScale): global Scale2D Scale2D = NewScale return #---------------------------------------------------------------------- def addLine3D(Canvas, P0, P1, NewColor=black): # P0 and P1 are 3D points [X,Y,Z] P0_2D = Project3D(P0) P1_2D = Project3D(P1) addLine(Canvas, INT(P0_2D[0]), INT(P0_2D[1]), INT(P1_2D[0]), INT(P1_2D[1]), NewColor) return #---------------------------------------------------------------------- # Add two points together of any number of dimensions. #---------------------------------------------------------------------- def addPoint(Q0,Q1): return [Q0[I] + Q1[I] for I in range(min(len(Q0),len(Q1)))] #---------------------------------------------------------------------- # Multiply all dimensions of a point by a scale factor. #---------------------------------------------------------------------- def scalePoint(Q, Scale=1.0): # Q is a 3D point return [Q[I] * Scale for I in range(len(Q))] #---------------------------------------------------------------------- # Plot a house where HouseOrigin is the front left corner, scaled # by a multiplier to set the size, and with the given color. # Notice the function Line is defined as local to AddHouse. # As far as Line is concerned, variables Canvas, HouseOrigin, Scale, # and NewColor are treated as global, even though they are local to # addHouse. #---------------------------------------------------------------------- def addHouse(Canvas, HouseOrigin, Scale, NewColor): # HouseOrigin = [X,Y,Z] 3D point def Line (P0,P1): addLine3D(Canvas, addPoint(HouseOrigin, scalePoint(P0,Scale)), addPoint(HouseOrigin, scalePoint(P1,Scale)), NewColor) return LLF = [0,0,0] LRF = [6,0,0] ULF = [0,3,0] URF = [6,3,0] MF = [3,6,0] LLB = [0,0,8] LRB = [6,0,8] ULB = [0,3,8] URB = [6,3,8] MB = [3,6,8] Line(LLF, LRF) Line(LLF, ULF) Line(LRF, URF) Line(ULF, MF ) Line(MF , URF) # Line(LLB, LRB) # Line(LLB, ULB) Line(LRB, URB) # Line(ULB, MB ) Line(MB , URB) # Line(LLF, LLB) # Line(ULF, ULB) Line(MF , MB ) Line(URF, URB) Line(LRF, LRB) return #---------------------------------------------------------------------- # Blend between numeric values P0 and P1 where T is the "knob" to turn # to select how far to pick the blend. At T=0 the result is P0, at T=1 # the result is P1, 0 and using the # Pythagorean Theorem. Can we make this more general? #---------------------------------------------------------------------- def Distance2D (X1,Y1,X2,Y2): DeltaX = X2 - X1 DeltaY = Y2 - Y1 return math.sqrt(DeltaX*DeltaX + DeltaY*DeltaY) #---------------------------------------------------------------------- # Draw a thick line between (color C1) and (color C2) #---------------------------------------------------------------------- def addThickLine(Canvas, X1,Y1,C1,X2,Y2,C2,R): D = Distance2D(X1,Y1,X2,Y2) Limit = INT(D) for I in range(Limit): T = float(I) / float(Limit-1) # T will be between 0 and 1 P = BlendPoints([X1,Y1], [X2,Y2], T) # P is a [X,Y] list of values (a point) addCircleFilled(Canvas, P[0], P[1], R, BlendColor(C1,C2,T)) return #---------------------------------------------------------------------- # Functions to fill in later. #---------------------------------------------------------------------- #def addThickLine2D(Canvas, P1,P2,R,NewColor): # P1,P2 are [X,Y] lists # addThickLine(Canvas,P1[0],P1[1],P2[0],P2[1],R,NewColor) # return #def addThickLine3D(Canvas, P1,P2,R,NewColor): # P1,P2 are [X,Y,Z] lists # Q1 = Project3D(P1) # Q2 = Project3D(P2) # addThickLine(Canvas,Q1[0],Q1[1],Q2[0],Q2[1],R,NewColor) # return #---------------------------------------------------------------------- # Draw a fat line blending colors from end-to-end #---------------------------------------------------------------------- def Main2D(): Canvas = makeEmptyPicture(800,600) addThickLine(Canvas, 100, 500, red, 700, 200, blue, 15) show(Canvas) return #---------------------------------------------------------------------- # Draw a neighborhood! #---------------------------------------------------------------------- def Main3D(): Canvas = makeEmptyPicture(800,600) SetOrigin2D([400,300]) SetScale2D(20.0) addLine3D(Canvas, [10,0,0], [-10,0,0], red) addLine3D(Canvas, [0,10,0], [0,-10,0], green) addLine3D(Canvas, [0,0,10], [0,0,-10], blue) for X in range(-100,+100, 10): for Z in range(-100, +100, 10): addHouse(Canvas, [X,0,Z], 1.0, black) show(Canvas) return